package com.ideal.manage.esms.service;

import com.alibaba.fastjson.JSONObject;
import com.ideal.manage.esms.bean.esms.EsmsDataBatch;
import com.ideal.manage.esms.bean.esms.EsmsIntegration;
import com.ideal.manage.esms.bean.esms.QEsmsDataBatch;
import com.ideal.manage.esms.bean.esms.QEsmsIntegration;
import com.ideal.manage.esms.bean.project.QStadningBookInfo;
import com.ideal.manage.esms.bean.project.StadningBookInfo;
import com.ideal.manage.esms.jpa.JpaRepositoryImpl;
import com.ideal.manage.esms.util.IntegrationUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;

@Service
public class IntegrationService {
    private Logger LOG = LoggerFactory.getLogger(IntegrationService.class);

    @Autowired
    private ElasticsearchService elasticsearchService;
    @Autowired
    private EntityManager entityManager;
    private JpaRepositoryImpl<EsmsDataBatch, Long> batchRepository;
    private JpaRepositoryImpl<EsmsIntegration, Long> integrationRepository;
    private JpaRepositoryImpl<StadningBookInfo, String> stadningBookRepository;

    @PostConstruct
    public void init() {
        this.batchRepository = new JpaRepositoryImpl<>(EsmsDataBatch.class, entityManager);
        this.integrationRepository = new JpaRepositoryImpl<>(EsmsIntegration.class, entityManager);
        this.stadningBookRepository = new JpaRepositoryImpl<>(StadningBookInfo.class, entityManager);
    }

    /**
     * 计算充电量和放电量
     */
    public Tuple<Double, Double> calDl(String ip, long start, long end) {
        List<JSONObject> list = elasticsearchService.searchByMatchFunction(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", ip))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end)),
                key -> key.equals("BatCurrent") || key.equals("BatVoltage"));
        double cdl = IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double batCurrent = list.get(idx).getDoubleValue("BatCurrent");
                    if (batCurrent <= 0) {
                        return 0;
                    }
                    //充电量为正数
                    return batCurrent * list.get(idx).getDoubleValue("BatVoltage");
                });
        double fdl = IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double batCurrent = list.get(idx).getDoubleValue("BatCurrent");
                    if (batCurrent > 0) {
                        return 0;
                    }
                    //放电量为负数
                    return Math.abs(batCurrent) * list.get(idx).getDoubleValue("BatVoltage");
                });
        return new Tuple<Double, Double>(cdl, fdl);
    }

    /**
     * 计算耗电费
     */
    public double calHdf(String projectId, long start, long end) {
        //查询电价
        List<TimeRange> ranges = this.getTimeRange(projectId);
        List<JSONObject> list = elasticsearchService.searchByMatchFunction(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end)),
                key -> key.equals("BatCurrent") || key.equals("BatVoltage"));
        return IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double batCurrent = list.get(idx).getDoubleValue("BatCurrent");
                    if (batCurrent <= 0) {
                        return 0;
                    }
                    return batCurrent
                            * list.get(idx).getDoubleValue("BatVoltage")
                            * TimeRange.getValue(ranges, list.get(idx).getLong("time"));
                });
    }

    /**
     * 计算省电费
     */
    public double calSdf(String projectId, long start, long end) {
        //查询电价
        List<TimeRange> ranges = this.getTimeRange(projectId);
        List<JSONObject> list = elasticsearchService.searchByMatchFunction(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end)),
                key -> key.equals("BatCurrent") || key.equals("BatVoltage"));
        double hdf = IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double batCurrent = list.get(idx).getDoubleValue("BatCurrent");
                    if (batCurrent <= 0) {
                        return 0;
                    }
                    return batCurrent
                            * list.get(idx).getDoubleValue("BatVoltage")
                            * TimeRange.getValue(ranges, list.get(idx).getLong("time"));
                });
        double fdf = IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double batCurrent = list.get(idx).getDoubleValue("BatCurrent");
                    if (batCurrent > 0) {
                        return 0;
                    }
                    return Math.abs(batCurrent)
                            * list.get(idx).getDoubleValue("BatVoltage")
                            * TimeRange.getValue(ranges, list.get(idx).getLong("time"));
                });
        return fdf - hdf;
    }

    /**
     * 当天充电量
     */
    public double calDtcdl(String projectId, long start, long end) {
        Map<String, JSONObject> map2009 = elasticsearchService.searchByMatchFunctionAsMap(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end))
                        .filter(QueryBuilders.existsQuery("2009")),
                key -> key.equals("2009") || key.equals("2010"));

        Map<String, JSONObject> map2010 = elasticsearchService.searchByMatchFunctionAsMap(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end))
                        .filter(QueryBuilders.existsQuery("2010")),
                key -> key.equals("2009") || key.equals("2010"));
        List<JSONObject> list = new ArrayList<>();
        for (Map.Entry<String, JSONObject> entry : map2009.entrySet()) {
            if (map2010.containsKey(entry.getKey())) {
                entry.getValue().put("2010", map2010.get(entry.getKey()).getDoubleValue("2010"));
            }
            list.add(entry.getValue());
        }
        return IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double current = list.get(idx).getDoubleValue("2010");
                    if (current <= 0) {
                        return 0;
                    }
                    //充电量为正数
                    return list.get(idx).getDoubleValue("2009")
                            * current;
                });
    }

    /**
     * 当天放电量
     */
    public double calDtfdl(String projectId, long start, long end) {
        Map<String, JSONObject> map2009 = elasticsearchService.searchByMatchFunctionAsMap(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end))
                        .filter(QueryBuilders.existsQuery("2009")),
                key -> key.equals("2009") || key.equals("2010"));

        Map<String, JSONObject> map2010 = elasticsearchService.searchByMatchFunctionAsMap(QueryBuilders.boolQuery()
                        .filter(QueryBuilders.termQuery("ip", projectId))
                        .filter(QueryBuilders.rangeQuery("time").gte(start).lte(end))
                        .filter(QueryBuilders.existsQuery("2010")),
                key -> key.equals("2009") || key.equals("2010"));
        List<JSONObject> list = new ArrayList<>();
        for (Map.Entry<String, JSONObject> entry : map2009.entrySet()) {
            if (map2010.containsKey(entry.getKey())) {
                entry.getValue().put("2010", map2010.get(entry.getKey()).getDoubleValue("2010"));
            }
            list.add(entry.getValue());
        }
        return IntegrationUtils.simpsonRule(list.size(), 0, list.size(),
                (idx, x) -> {
                    double current = list.get(idx).getDoubleValue("2010");
                    if (current > 0) {
                        return 0;
                    }
                    return list.get(idx).getDoubleValue("2009")
                            * Math.abs(current);
                });
    }

    private List<TimeRange> getTimeRange(String ip) {
        QStadningBookInfo qStadningBookInfo = QStadningBookInfo.stadningBookInfo;
        List<StadningBookInfo> infos = stadningBookRepository.query()
                .select(qStadningBookInfo)
                .from(qStadningBookInfo)
                .where(qStadningBookInfo.account.parameterType.eq(6)
                        .and(qStadningBookInfo.account.objName.eq(1)
                                .and(qStadningBookInfo.isValid.eq(1))
                                .and(qStadningBookInfo.isValid.eq(1))
                                .and(qStadningBookInfo.project.ip.eq(ip))))
                .fetch();
        List<TimeRange> ranges = new ArrayList<>();
        for (StadningBookInfo info : infos) {
            String name = info.getAccount().getName();
            String t = name.split("/")[1];
            String times[] = t.split(";");
            for (String time : times) {
                ranges.add(new TimeRange(time, NumberUtils.toDouble(info.getVal())));
            }
        }
        return ranges;
    }

    @Transactional
    public void autoCal(long last) {
        QEsmsDataBatch qBatch = QEsmsDataBatch.esmsDataBatch;
        EsmsDataBatch batch = batchRepository.query()
                .select(qBatch)
                .from(qBatch)
                .limit(1)
                .orderBy(qBatch.time.desc())
                .where(qBatch.time.loe(last).and(qBatch.integrationCal.eq(0)))
                .fetchOne();
        if (batch == null) {
            return;
        }
        LOG.debug("计算积分 {} {}", new Date(batch.getTime()), batch.getId());
        Map.Entry<LocalDateTime, LocalDateTime> entry = this.findFirst(batch.getTime());
        long start = entry.getKey().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        long end = entry.getValue().toInstant(ZoneOffset.of("+8")).toEpochMilli() + 999;

        QEsmsIntegration qIntegration = QEsmsIntegration.esmsIntegration;
        EsmsIntegration cdl = integrationRepository.findOne(qIntegration.type.eq("cdl")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (cdl == null) {
            cdl = new EsmsIntegration();
        }
        Tuple<Double, Double> dl = this.calDl(batch.getProjectIp(), start, end);
        cdl.setProjectIp(batch.getProjectIp());
        cdl.setType("cdl");
        cdl.setCreateTime(new Date());
        cdl.setCalTime(new Date(start));
        cdl.setVal(dl.v1() * 0.001);
        integrationRepository.save(cdl);
        LOG.debug("充电量 {} ip {} start {} end {}", cdl.getVal(), batch.getProjectIp(), start, end);


        EsmsIntegration fdl = integrationRepository.findOne(qIntegration.type.eq("fdl")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (fdl == null) {
            fdl = new EsmsIntegration();
        }
        fdl.setProjectIp(batch.getProjectIp());
        fdl.setType("fdl");
        fdl.setCreateTime(new Date());
        fdl.setCalTime(new Date(start));
        fdl.setVal(dl.v2() * 0.001);
        integrationRepository.save(fdl);
        LOG.debug("放电量 {} ip {} start {} end {}", fdl.getVal(), batch.getProjectIp(), start, end);


        EsmsIntegration hdf = integrationRepository.findOne(qIntegration.type.eq("hdf")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (hdf == null) {
            hdf = new EsmsIntegration();
        }
        hdf.setProjectIp(batch.getProjectIp());
        hdf.setType("hdf");
        hdf.setCreateTime(new Date());
        hdf.setCalTime(new Date(start));
        hdf.setVal(Math.abs(this.calHdf(batch.getProjectIp(), start, end)));
        integrationRepository.save(hdf);
        LOG.debug("耗电费 {} ip {} start {} end {}", hdf.getVal(), batch.getProjectIp(), start, end);

        EsmsIntegration sdf = integrationRepository.findOne(qIntegration.type.eq("sdf")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (sdf == null) {
            sdf = new EsmsIntegration();
        }
        sdf.setProjectIp(batch.getProjectIp());
        sdf.setType("sdf");
        sdf.setCreateTime(new Date());
        sdf.setCalTime(new Date(start));
        sdf.setVal(Math.abs(this.calSdf(batch.getProjectIp(), start, end)));
        integrationRepository.save(sdf);
        LOG.debug("省电费 {} ip {} start {} end {}", sdf.getVal(), batch.getProjectIp(), start, end);

        EsmsIntegration dtcdl = integrationRepository.findOne(qIntegration.type.eq("dtcdl")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (dtcdl == null) {
            dtcdl = new EsmsIntegration();
        }
        dtcdl.setProjectIp(batch.getProjectIp());
        dtcdl.setType("dtcdl");
        dtcdl.setCreateTime(new Date());
        dtcdl.setCalTime(new Date(start));
        dtcdl.setVal(this.calDtcdl(batch.getProjectIp(), start, end));
        integrationRepository.save(dtcdl);
        LOG.debug("当天充电量 {}  ip {} start {} end {}", dtcdl.getVal(), batch.getProjectIp(), start, end);

        EsmsIntegration dtfdl = integrationRepository.findOne(qIntegration.type.eq("dtfdl")
                .and(qIntegration.calTime.eq(new Date(start)))
                .and(qIntegration.projectIp.eq(batch.getProjectIp())));
        if (dtfdl == null) {
            dtfdl = new EsmsIntegration();
        }
        dtfdl.setProjectIp(batch.getProjectIp());
        dtfdl.setType("dtfdl");
        dtfdl.setCreateTime(new Date());
        dtfdl.setCalTime(new Date(start));
        dtfdl.setVal(this.calDtfdl(batch.getProjectIp(), start, end));
        integrationRepository.save(dtfdl);
        LOG.debug("当天放电量 {} ip {} start {} end {}", dtfdl.getVal(), batch.getProjectIp(), start, end);

        long updateCount = batchRepository.update(qBatch)
                .set(qBatch.integrationCal, 1)
                .where(qBatch.time.goe(start).and(qBatch.time.loe(end)))
                .execute();
        LOG.debug("更新数据 {}", updateCount);
    }

    public Map.Entry<LocalDateTime, LocalDateTime> findFirst(long startTime) {
        LocalDateTime first = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime),
                TimeZone.getDefault().toZoneId());
        LocalDateTime start = LocalDateTime.of(first.getYear(), first.getMonth(), first.getDayOfMonth(),
                first.getHour(), 0, 0);
        LocalDateTime end = LocalDateTime.of(first.getYear(), first.getMonth(), first.getDayOfMonth(),
                first.getHour(), 0, 0);
        int m = first.getMinute();
        if (m <= 15) {
            start = start.withMinute(0);
            end = end.withMinute(14).withSecond(59);
        } else if (m <= 30) {
            start = start.withMinute(15);
            end = end.withMinute(29).withSecond(59);
        } else if (m <= 45) {
            start = start.withMinute(30);
            end = end.withMinute(44).withSecond(59);
        } else {
            start = start.withMinute(45);
            end = end.withMinute(59).withSecond(59);
        }
        return new HashMap.SimpleImmutableEntry<LocalDateTime, LocalDateTime>(start, end);
    }
}
